import c


cconres_t = c.ExternType("cconres_t")
stream_t = c.ExternType("stream_t")


class CompilationError(Exception):
    pass
class PathError(CompilationError):
    pass


class Construct(object):
    _id = 0
    FLAG_DYNAMIC = 1
    FLAG_NESTED = 2
    
    def __init__(self, name, flags = 0):
        assert name is None or type(name) is str, "Name must be a string or None"
        Construct._id += 1
        self.name = name
        self.flags = flags
        self.nested_scope = False
        if name is None:
            self.id = "%d" % (Construct._id,)
            self.membername = "m_%d" % (Construct._id,)
        else:
            self.id = "%s%d" % (name, Construct._id)
            self.membername = "m_%s" % (name,)
        self.prefix = "ccon_%s" % (self.id,)
        self.parent = None
        self._prepared = False
        self.ctype = c.ExternType(self.prefix + "_t")
        self.cparse = c.ExternFunc(self.prefix + "_parse")
        self.cbuild = c.ExternFunc(self.prefix + "_build")
    
    def _topsort(self, parent, coll):
        if self._prepared:
            return
        self._prepared = True
        self.parent = parent
        for child in self.scan():
            #if self.nested_scope:
            child._topsort(self, coll)
            #else:
            #    child._topsort(self.parent, coll)
        coll.append(self)
    
    def prepare(self):
        coll = []
        self._topsort(None, coll)
        for con in coll:
            con.ctype = con.gen_type()
        for con in coll:
            con.cparse = con.gen_parse()
            con.cbuild = con.gen_build()
        return coll
    
    def scan(self):
        return []
    
    def generate(self, mod):
        for con in self.prepare():
            con.cparse = con.gen_parse()
            con.cbuild = con.gen_build()
            if con.cparse is not None:
                mod.add(con.cparse)
            if con.cbuild is not None:
                mod.add(con.cbuild)
            if con.ctype is not None:
                mod.add(con.ctype)
    
    def proto_parse(self):
        return c.Func(self.prefix + "_parse", cconres_t, 
            c.VarDef(c.Ptr(self.ctype), "obj"), 
            c.VarDef(c.Ptr(stream_t), "stream")
        )
    def proto_build(self):
        return c.Func(self.prefix + "_build", cconres_t, 
            c.VarDef(c.Ptr(self.ctype), "obj"), 
            c.VarDef(c.Ptr(stream_t), "stream")
        )
    
    def gen_parse(self):
        pass
    def gen_build(self):
        pass
    def gen_type(self):
        pass
    
    def invoke_parse(self, outvar, block):
        block.append(self.cparse.invoke("_res", outvar, "stream"))
    def invoke_build(self, invar, block):
        block.append(self.cbuild.invoke("_res", invar, "stream"))


class Subconstruct(Construct):
    def __init__(self, subcon, inherit_flags = True):
        Construct.__init__(self, subcon.name)
        self.subcon = subcon
        self.inherit_flags = inherit_flags
    def scan(self):
        if self.inherit_flags:
            self.flags = self.subcon.flags
        return [self.subcon]
    def gen_type(self):
        return self.subcon.gen_type()
    def gen_parse(self):
        return self.subcon.gen_parse()
    def gen_build(self):
        return self.subcon.gen_build()


class Dynamic(object):
    def __add__(self, other):
        return BinExpr("+", self, other)
    def __sub__(self, other):
        return BinExpr("-", self, other)
    def __mul__(self, other):
        return BinExpr("*", self, other)
    def __div__(self, other):
        return BinExpr("/", self, other)
    def __radd__(self, other):
        return BinExpr("+", other, self)
    def __rsub__(self, other):
        return BinExpr("-", other, self)
    def __rmul__(self, other):
        return BinExpr("*", other, self)
    def __rdiv__(self, other):
        return BinExpr("/", other, self)

class BinExpr(Dynamic):
    def __init__(self, op, lhs, rhs):
        self.op = op
        self.lhs = lhs
        self.rhs = rhs
    def __call__(self, con):
        if isinstance(self.lhs, Path):
            lhs = self.lhs(con)
        else:
            lhs = self.lhs
        if isinstance(self.rhs, Path):
            rhs = self.rhs(con)
        else:
            rhs = self.rhs
        return "%s %s %s" % (lhs, self.op, rhs)

class Path(Dynamic):
    def __init__(self, name, parent = None):
        self.__name = name
        self.__parent = parent
    def __call__(self, con):
        if self.__parent is None:
            return self.__name
        #if not con.ctype.has_member(self.__name):
        #    raise PathError("Unknown member %r" % (self.__name,))
        p = self.__parent(con.parent)
        if self.__name == "parent":
            return "%s->%s" % (p, self.__name)
        else:
            return "%s->%s" % (p, self.__name)
    def __getattr__(self, name):
        if name == "_":
            return Path("parent", self)
        else:
            return Path("m_%s" % (name,), self)

this = Path("obj")._


class NumericField(Construct):
    c_type = None
    def gen_parse(self):
        return c.ExternFunc("ccon_%s_parse" % (self.__class__.__name__.lower(),))
    def gen_build(self):
        return c.ExternFunc("ccon_%s_build" % (self.__class__.__name__.lower(),))
    def gen_type(self):
        return self.c_type

class UBInt8(NumericField):    c_type = c.c_uchar
class UBInt16(NumericField):   c_type = c.c_ushort
class UBInt32(NumericField):   c_type = c.c_ulong
class SBInt8(NumericField):    c_type = c.c_schar
class SBInt16(NumericField):   c_type = c.c_sshort
class SBInt32(NumericField):   c_type = c.c_slong
class ULInt8(NumericField):    c_type = c.c_uchar
class ULInt16(NumericField):   c_type = c.c_ushort
class ULInt32(NumericField):   c_type = c.c_ulong
class SLInt8(NumericField):    c_type = c.c_schar
class SLInt16(NumericField):   c_type = c.c_sshort
class SLInt32(NumericField):   c_type = c.c_slong


class Struct(Construct):
    def __init__(self, name, *subcons):
        Construct.__init__(self, name)
        self.subcons = subcons
        self.nested_scope = True
    
    def gen_type(self):
        t = c.Struct(self.prefix + "_t")
        if self.parent is not None:
            #t.append(c.Ptr(c.c_void), "parent")
            t.append(c.Ptr(self.parent.ctype), "parent")
        for sc in self.subcons:
            t.append(sc.ctype, sc.membername)
        return t
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.Stmt("PROPAGATABLE"))
        for sc in self.subcons:
            if sc.ctype.has_member("parent"):
                f.body.append(c.Stmt("obj->%s.parent = obj", sc.membername))
            sc.invoke_parse("&obj->%s" % (sc.membername,), f.body)
            f.body.append(c.Stmt("PROPAGATE"))
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f
    
    def scan(self):
        for sc in self.subcons:
            self.flags |= sc.flags
        return self.subcons


class StaticField(Construct):
    def __init__(self, name, length):
        Construct.__init__(self, name)
        self.length = length
    def gen_type(self):
        return c.Typedef(c.c_char, self.prefix + "_t", [self.length])
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.VarDef(c.c_int, "count"))
        f.body.append(c.Stmt("count = stream->read(stream->self, (char*)obj, %d)", self.length))
        f.body.append(c.Stmt("if (count != %d) return CCONRES_END_OF_STREAM", self.length))
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f


class DynamicField(Construct):
    def __init__(self, name, lengthexpr):
        Construct.__init__(self, name)
        self.lengthexpr = lengthexpr
        self.flags |= self.FLAG_DYNAMIC
    def gen_type(self):
        t = c.Struct(self.prefix + "_t")
        #t.append(c.Ptr(c.c_void), "parent")
        t.append(c.Ptr(self.parent.ctype), "parent")
        t.append(c.Ptr(c.c_char), "data")
        return t
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.VarDef(c.c_int, "count"))
        f.body.append(c.VarDef(c.c_int, "length"))
        f.body.append(c.Stmt("length = %s", self.lengthexpr(self)))
        f.body.append(c.Stmt("obj->data = (char*)malloc(length)"))
        f.body.append(c.Stmt("count = stream->read(stream->self, obj->data, length)"))
        f.body.append(c.Stmt("if (count != length) return CCONRES_END_OF_STREAM"))
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f


class StaticArray(Subconstruct):
    def __init__(self, count, subcon):
        Subconstruct.__init__(self, subcon)
        self.count = count
    def gen_type(self):
        if self.flags & self.FLAG_DYNAMIC:
            t = c.Struct(self.prefix + "_t")
            #t.append(c.Ptr(c.c_void), "parent")
            t.append(c.Ptr(self.parent.ctype), "parent")
            t.append(self.subcon.ctype, "elements", [self.count])
        else:
            t = c.Typedef(self.subcon.ctype, self.prefix + "_t", [self.count])
        return t
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.Stmt("PROPAGATABLE"))
        f.body.append(c.VarDef(c.c_int, "i"))
        f2 = c.ForStmt("i = 0; i < %d; i++", self.count)
        if self.subcon.ctype.has_member("parent"):
            f2.body.append(c.Stmt("obj->elements[i].parent = obj"))
        if self.flags & self.FLAG_DYNAMIC:
            self.subcon.invoke_parse("&obj->elements[i]", f2.body)
        else:
            self.subcon.invoke_parse("&obj[i]", f2.body)
        f2.body.append(c.Stmt("PROPAGATE"))
        f.body.append(f2)
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f


class DynamicArray(Subconstruct):
    def __init__(self, countexpr, subcon):
        Subconstruct.__init__(self, subcon)
        self.flags |= self.FLAG_DYNAMIC
        self.countexpr = countexpr
    def gen_type(self):
        t = c.Struct(self.prefix + "_t")
        #t.append(c.Ptr(c.c_void), "parent")
        t.append(c.Ptr(self.parent.ctype), "parent")
        t.append(c.Ptr(self.subcon.ctype), "elements")
        return t
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.Stmt("PROPAGATABLE"))
        f.body.append(c.VarDef(c.c_int, "i"))
        f.body.append(c.VarDef(c.c_int, "count"))
        f.body.append(c.Stmt("count = %s", self.countexpr(self)))
        f.body.append(c.Stmt("obj->elements = (%s*)malloc(count * sizeof(%s))", 
            self.subcon.ctype, self.subcon.ctype))
        f2 = c.ForStmt("i = 0; i < count; i++")
        f2.body.append(c.Stmt("obj->elements[i].parent = obj"))
        self.subcon.invoke_parse("&obj->elements[i]", f2.body)
        f2.body.append(c.Stmt("PROPAGATE"))
        f.body.append(f2)
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f   


class DynamicValue(Construct):
    def __init__(self, name, expr, type = c.c_int):
        Construct.__init__(self, name)
        self.type = type
        self.expr = expr
    
    def gen_type(self):
        return self.type
    
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.Stmt("return %s", self.expr(self)))


class Anchor(Construct):
    def gen_type(self):
        return c.c_int
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.Stmt("*obj = stream->tell(stream->self)"))
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f


class Pointer(Subconstruct):
    def __init__(self, offsetexpr, subcon):
        Subconstruct.__init__(self, subcon)
        self.offsetexpr = offsetexpr
        self.flags |= self.FLAG_DYNAMIC
    def gen_parse(self):
        f = self.proto_parse()
        f.body.append(c.Stmt("PROPAGATABLE"))
        f.body.append(c.VarDef(c.c_int, "currpos"))
        f.body.append(c.VarDef(c.c_int, "offset"))
        f.body.append(c.Stmt("offset = %s", self.offsetexpr(self)))
        f.body.append(c.Stmt("currpos = stream->tell(stream->self)"))
        f.body.append(c.Stmt("stream->seek(stream->self, offset)"))
        self.subcon.invoke_parse("obj", f.body)
        f.body.append(c.Stmt("PROPAGATE"))
        f.body.append(c.Stmt("stream->seek(stream->self, currpos)"))
        f.body.append(c.Stmt("return CCONRES_OKAY"))
        return f


class LateBound(Construct):
    def __init__(self, name, func):
        Construct.__init__(self, name)
        self.func = func
    def scan(self):
        self.subcon = self.func()
        return [self.subcon]
    def gen_type(self):
        return self.subcon.ctype
    def gen_parse(self):
        return self.subcon.cparse
    def gen_build(self):
        return self.subcon.cbuild



#===============================================================================
# Macros
#===============================================================================
def Field(name, length):
    if type(length) is int:
        return StaticField(name, length)
    else:
        return DynamicField(name, length)


def Array(count, subcon):
    if type(count) is int:
        return StaticArray(count, subcon)
    else:
        return DynamicArray(count, subcon)


#===============================================================================
# Generation API
#===============================================================================
def generate(modname, *cons):
    mod = c.Module(modname)
    mod.includes.add('"ccon.h"')
    for con in cons:
        con.generate(mod)
        if con.cparse:
            mod.add(c.Macro("ccon_%s_parse" % (con.name,), con.cparse.name))
        if con.cbuild:
            mod.add(c.Macro("ccon_%s_build" % (con.name,), con.cbuild.name))
        if con.ctype:
            mod.add(c.Macro("ccon_%s_t" % (con.name,), con.ctype.name))
    mod.generate()





























